//-------------------------------------------------------------------
//	Copyright (c) 2013-2014  Zhirnov Andrey
//	This file is part of the "UX OpwnGL Framework" project.
//	See copyright notice in "UX_GL_Framework.h".
//-------------------------------------------------------------------

#pragma once

#include "VoxelsBlock.h"

namespace Voxels
{

	//
	// Voxels Block Cache
	//

	class VoxelsBlockCache
	{
	// types
	public:
		typedef VoxelsBlock::BlockInfo				BlockInfo;
		typedef VoxelsBlock::VoxelsUniData_t		VoxelsUniData_t;
		typedef VoxelsBlock::VoxelsUniConstData_t	VoxelsUniConstData_t;
		
	private:
		enum {
			BORDER	= Helpers::BORDER
		};

		
	// variables
	private:
		binary_array_t			_data;
		Ptr< const BlockInfo >	_info;


	// methods
	public:
		VoxelsBlockCache () : _info(null)
		{}

		explicit
		VoxelsBlockCache (const BlockInfo &info) : _info(&info)
		{}

		bool Create (const VoxelsBlock &block);
		bool AllocData ();
		
		bool IsValid () const;

		bool CopyTo (const ivec3 &dstDir, INOUT VoxelsBlock &block) const;
		
		VoxelsUniData_t			Data ()			{ return _data; }
		VoxelsUniConstData_t	Data ()	const	{ return _data; }


	private:
		ivec3	_BlockSize ()			const	{ return _info->blockSize.To<ivec3>(); }
		uint	_BytePerVoxel ()		const	{ return _info->bytePerVoxel; }

		usize	_GetOffset (const ivec3 &dir) const;

		usize	_GetFaceDataSize (const ivec3 &dir) const;
		ivec3	_GetFaceSizes (const ivec3 &dir) const;

		usize	_GetDataSize () const;

		bool	_SetFace (const VoxelsBlock &block, usize offset, const ivec3 &startCoord, const ivec3 &size);

		VoxelsUniConstData_t _GetFace (const ivec3 &dir) const;
		VoxelsUniConstData_t _GetVoxel (VoxelsUniConstData_t data, const ivec3 &coord, const ivec3 &faceSize) const;

		static ivec3 _DstDirToSrc (const ivec3 &dir)	{ return -dir; }
	};

	
	
/*
=================================================
	Create
=================================================
*/
	inline bool VoxelsBlockCache::Create (const VoxelsBlock &block)
	{
		const ivec3	size = _BlockSize() - 1;

		CHECK_RETURN( AllocData() );

		for (int z = -BORDER; z <= BORDER; ++z)
		{
			for (int y = -BORDER; y <= BORDER; ++y)
			{
				for (int x = -BORDER; x <= BORDER; ++x)
				{
					const ivec3	dir	= ivec3( x, y, z );
					const uint	idx	= Helpers::DirectionToIndex( dir );

					if ( IsZero( dir ) )
						continue;

					const usize	offset		= _GetOffset( dir );
					const ivec3	face_size	= (1 - dir * dir) * size;
					const ivec3	start_coord	= Clamp( dir * size, ivec3(0), size );

					CHECK_RETURN( _SetFace( block, offset, start_coord, face_size+1 ) );
				}
			}
		}
		return true;
	}
	
/*
=================================================
	Create
=================================================
*/
	inline bool VoxelsBlockCache::AllocData ()
	{
		CHECK_ALLOC( _data.Resize( _GetDataSize(), false ) );
		return true;
	}

/*
=================================================
	_SetFace
=================================================
*/
	inline bool VoxelsBlockCache::_SetFace (const VoxelsBlock &block, usize offset, const ivec3 &startCoord, const ivec3 &size)
	{
		for (int z = 0; z < size.z; ++z)
		{
			for (int y = 0; y < size.y; ++y)
			{
				for (int x = 0; x < size.x; ++x)
				{
					const ivec3	c	= ivec3( x, y, z );
					const ivec3	s	= startCoord + c;
					const usize	off	= x + y * size.x+ + z * size.x * size.y + offset;

					VoxelsUniConstData_t voxel = block.GetVoxel( s );

					ASSERT( off + voxel.Size() <= _data.Size() );
					ASSERT( voxel.Size() == _BytePerVoxel() );

					MemCopy( _data.ptr() + off, voxel.ptr(), voxel.Size() );
				}
			}
		}
		return true;
	}
	
/*
=================================================
	_GetFace
=================================================
*/
	inline VoxelsBlockCache::VoxelsUniConstData_t VoxelsBlockCache::_GetFace (const ivec3 &dir) const
	{
		const usize	offset	= _GetOffset( dir );
		const usize	size	= _GetFaceDataSize( dir );

		ASSERT( offset + size <= _data.Size() );
		return VoxelsUniConstData_t( _data.ptr() + offset, size );
	}
	
/*
=================================================
	_GetOffset
=================================================
*/
	inline usize VoxelsBlockCache::_GetOffset (const ivec3 &dir) const
	{
		usize	offset = 0;

		for (int z = -BORDER; z <= BORDER; ++z)
		{
			for (int y = -BORDER; y <= BORDER; ++y)
			{
				for (int x = -BORDER; x <= BORDER; ++x)
				{
					const ivec3	c = ivec3( x, y, z );

					if ( All( c == dir ) )
						break;

					offset += _GetFaceDataSize( c );
				}
			}
		}
		return offset;
	}
	
/*
=================================================
	_GetDataSize
=================================================
*/
	inline usize VoxelsBlockCache::_GetDataSize () const
	{
		return _GetOffset( ivec3(2) );
	}

/*
=================================================
	_GetFaceDataSize
=================================================
*/
	inline usize VoxelsBlockCache::_GetFaceDataSize (const ivec3 &dir) const
	{
		return _GetFaceSizes( dir ).Volume() * _BytePerVoxel();
	}
	
/*
=================================================
	_GetFaceSizes
=================================================
*/
	inline ivec3 VoxelsBlockCache::_GetFaceSizes (const ivec3 &dir) const
	{
		if ( IsZero( dir ) )
			return ivec3(0);

		if ( Any( dir < ivec3(-BORDER) ) or Any( dir > ivec3(BORDER) ) )
		{
			WARNING( "invalid face direction!" );
			return ivec3(0);
		}

		const ivec3	block_size	= _BlockSize() - 1;
		const ivec3	face_size	= (1 - dir * dir) * block_size + 1;
		return face_size;
	}

/*
=================================================
	IsValid
=================================================
*/
	inline bool VoxelsBlockCache::IsValid () const
	{
		return _data.Size() == _GetDataSize();
	}

/*
=================================================
	CopyTo
=================================================
*/
	inline bool VoxelsBlockCache::CopyTo (const ivec3 &dstDir, INOUT VoxelsBlock &block) const
	{
		CHECK_ARGUMENT( IsNotZero( block.Border() ) );

		const ivec3				src_dir	= _DstDirToSrc( dstDir );
		VoxelsUniConstData_t	face	= _GetFace( src_dir );
		
		const ivec3	block_size	= _BlockSize() - 1;
		const ivec3	dst_start	= Clamp( dstDir * (block_size+1), ivec3(-1), block_size+1 );
		const ivec3	face_size	= (1 - dstDir * dstDir) * block_size;

		for (int z = 0; z <= face_size.z; ++z)
		{
			for (int y = 0; y <= face_size.y; ++y)
			{
				for (int x = 0; x <= face_size.x; ++x)
				{
					const ivec3	c = ivec3( x, y, z );
					const ivec3	d = dst_start + c;

					VoxelsUniConstData_t voxel = _GetVoxel( face, c, face_size+1 );

					block.SetVoxel( d, voxel );
				}
			}
		}
		return true;
	}
	
/*
=================================================
	_GetVoxel
=================================================
*/
	inline VoxelsBlockCache::VoxelsUniConstData_t
		VoxelsBlockCache::_GetVoxel (VoxelsUniConstData_t data, const ivec3 &coord, const ivec3 &faceSize) const
	{
		const usize i = coord.x + coord.y * faceSize.x + coord.z * faceSize.x * faceSize.y;
		ASSERT( i + _BytePerVoxel() <= data.Size() );
		return VoxelsUniConstData_t( data.ptr() + i, _BytePerVoxel() );
	}


}	// Voxels
